/* CUSTOM DETECTION!  Stub below is just a base for autogenerator 
alert tcp $EXTERNAL_NET any -> $HOME_NET 3389 (msg:"EXPLOIT Microsoft Windows RemoteDesktop connect-initial pdu remote code execution attempt"; flow:to_server, established; content:"|03 00|"; depth:2; content:"|f0 80 7f 65|"; fast_pattern; metadata:policy balanced-ips drop, policy security-ips drop, service rdp; reference:cve,2012-0002; reference:url,technet.microsoft.com/en-us/security/bulletin/ms12-020; classtype:attempted-admin; sid:21619;)
*/
/*
 * Vuln Title: XXXX
 *
 * Copyright (C) 2005-2010 Sourcefire, Inc. All Rights Reserved
 *
 * Written by XXXX, Sourcefire VRT <XXXX@sourcefire.com>
 *
 * Auto-generated by XXXX
 *
 * This file may contain proprietary rules that were created, tested and
 * certified by Sourcefire, Inc. (the "VRT Certified Rules") as well as
 * rules that were created by Sourcefire and other third parties and
 * distributed under the GNU General Public License (the "GPL Rules").  The
 * VRT Certified Rules contained in this file are the property of
 * Sourcefire, Inc. Copyright 2005 Sourcefire, Inc. All Rights Reserved.
 * The GPL Rules created by Sourcefire, Inc. are the property of
 * Sourcefire, Inc. Copyright 2002-2005 Sourcefire, Inc. All Rights
 * Reserved.  All other GPL Rules are owned and copyrighted by their
 * respective owners (please see www.snort.org/contributors for a list of
 * owners and their respective copyrights).  In order to determine what
 * rules are VRT Certified Rules or GPL Rules, please refer to the VRT
 * Certified Rules License Agreement.
 */

#include "sf_snort_plugin_api.h"
#include "sf_snort_packet.h"

#include "so-util_ber.h"

//#define DEBUG
#ifdef DEBUG
#define DEBUG_SO(code) code
#else
#define DEBUG_SO(code)
#endif

/* declare detection functions */
int rule21619eval(void *p);

/* declare rule data structures */
/* flow:established, to_server; */
static FlowFlags rule21619flow0 = 
{
    FLOW_ESTABLISHED|FLOW_TO_SERVER
};

static RuleOption rule21619option0 =
{
    OPTION_TYPE_FLOWFLAGS,
    {
        &rule21619flow0
    }
};
// content:"|03 00|", depth 2; 
static ContentInfo rule21619content1 = 
{
    (uint8_t *) "|03 00|", /* pattern (now in snort content format) */
    2, /* depth */
    0, /* offset */
    CONTENT_BUF_NORMALIZED, /* flags */
    NULL, /* holder for boyer/moore PTR */
    NULL, /* more holder info - byteform */
    0, /* byteform length */
    0 /* increment length*/
};

static RuleOption rule21619option1 = 
{
    OPTION_TYPE_CONTENT,
    {
        &rule21619content1
    }
};
// content:"|F0 80 7F 65|", depth 0, fast_pattern; 
static ContentInfo rule21619content2 = 
{
    (uint8_t *) "|F0 80 7F 65|", /* pattern (now in snort content format) */
    0, /* depth */
    0, /* offset */
    CONTENT_FAST_PATTERN|CONTENT_BUF_NORMALIZED, /* flags */
    NULL, /* holder for boyer/moore PTR */
    NULL, /* more holder info - byteform */
    0, /* byteform length */
    0 /* increment length*/
};

static RuleOption rule21619option2 = 
{
    OPTION_TYPE_CONTENT,
    {
        &rule21619content2
    }
};

/* references for sid 21619 */
/* reference: cve "2012-0002"; */
static RuleReference rule21619ref1 = 
{
    "cve", /* type */
    "2012-0002" /* value */
};

/* reference: url "technet.microsoft.com/en-us/security/bulletin/ms12-020"; */
static RuleReference rule21619ref2 = 
{
    "url", /* type */
    "technet.microsoft.com/en-us/security/bulletin/ms12-020" /* value */
};

static RuleReference *rule21619refs[] =
{
    &rule21619ref1,
    &rule21619ref2,
    NULL
};
/* metadata for sid 21619 */
/* metadata:service rdp, policy balanced-ips drop, policy security-ips drop; */
static RuleMetaData rule21619service1 = 
{
    "service rdp"
};


static RuleMetaData rule21619policy1 = 
{
    "policy balanced-ips drop"
};

static RuleMetaData rule21619policy2 = 
{
    "policy security-ips drop"
};

static RuleMetaData rule21619policy3 = 
{
    "policy max-detect-ips drop"
};

static RuleMetaData *rule21619metadata[] =
{
    &rule21619service1,
    &rule21619policy1,
    &rule21619policy2,
    &rule21619policy3,
    NULL
};

RuleOption *rule21619options[] =
{
    &rule21619option0,
    &rule21619option1,
    &rule21619option2,
    NULL
};

Rule rule21619 = {
   /* rule header, akin to => tcp any any -> any any */
   {
       IPPROTO_TCP, /* proto */
       "$EXTERNAL_NET", /* SRCIP     */
       "any", /* SRCPORT   */
       0, /* DIRECTION */
       "$HOME_NET", /* DSTIP     */
       "3389", /* DSTPORT   */
   },
   /* metadata */
   { 
       3,  /* genid */
       21619, /* sigid */
       5, /* revision */
       "attempted-admin", /* classification */
       0,  /* hardcoded priority XXX NOT PROVIDED BY GRAMMAR YET! */
       "OS-WINDOWS Microsoft Windows RemoteDesktop connect-initial pdu remote code execution attempt",     /* message */
       rule21619refs /* ptr to references */
       ,rule21619metadata
   },
   rule21619options, /* ptr to rule options */
   &rule21619eval, /* use the built in detection function */
   0 /* am I initialized yet? */
};


/* detection functions */
int rule21619eval(void *p) {
   const uint8_t *cursor_normal = 0;
   SFSnortPacket *sp = (SFSnortPacket *) p;

   const uint8_t *cursor_targetParameters = 0;
   BER_ELEMENT ber_element;
   BER_ELEMENT ber_element2; // for special check for Wyse OS
   uint32_t tp_dp_maxChannelIds, tp_dp_maxUserIds, mp_dp_maxChannelIds;
   int retval;

   DEBUG_SO(fprintf(stderr, "rule21619eval enter\n");)

   if(sp == NULL)
      return RULE_NOMATCH;

   if(sp->payload == NULL)
      return RULE_NOMATCH;
    
   // flow:established, to_server;
   if(checkFlow(p, rule21619options[0]->option_u.flowFlags) <= 0)
      return RULE_NOMATCH;

   // Nominal check that we're in the right type of packet -- "TPKT version:3"
   // content:"|03 00|", depth 2;
   if(contentMatch(p, rule21619options[1]->option_u.content, &cursor_normal) <= 0)
      return RULE_NOMATCH;

   // |7f 65| is "Connect-Initial"
   // |F0 80| is Data TPDU and EOT from the preceding TPKT.  I believe this should
   // be an okay check to denote we are in the right place and I believe doing the
   // check this way avoids any potential evasions due to messing with the length
   // field (if that's even possible since the TPKT parts don't look like BER).
   // In any case, this will give us a better fast_pattern and I think it's safe.
   // If I'm proven wrong, simply remove the "|F0 80|" and we'll still have very
   // solid assurance we're in the right place due to the additional checks for
   // fields as we get to what we're looking for.
   // Can we have more than one Connect-Initial PDU?  Logic says "no," but...

   // content:"|F0 80 7F 65|", depth 0, fast_pattern;
   if(contentMatch(p, rule21619options[2]->option_u.content, &cursor_normal) <= 0)
      return RULE_NOMATCH;

   // Back up two bytes so we can process the BER data like normal
   cursor_normal -= 2;

   BER_DATA(0x65); // MCS_TYPE_CONNECTINITIAL   
   BER_SKIP(0x04); // Connect-Initial::callingDomainSelector
   BER_SKIP(0x04); // Connect-Initial::calledDomainSelector
   BER_SKIP(0x01); // Connect-Initial::upwardFlag

   cursor_targetParameters = cursor_normal; // Store for jumping over this later

   BER_DATA(0x30); // Connect-Initial::targetParameters

   DEBUG_SO(fprintf(stderr, "found Connect-Initial::targetParameters\n");)

   BER_EXTRACT_INT(ber_element); // Connect-Initial::targetParameters::DomainParameters::maxChannelIds
   tp_dp_maxChannelIds = ber_element.data.int_val;
   DEBUG_SO(fprintf(stderr, "target maxChannelIds = 0x%04x (%d)\n", tp_dp_maxChannelIds, tp_dp_maxChannelIds);)

   // Condition 1
   if(tp_dp_maxChannelIds < 6) {
      DEBUG_SO(fprintf(stderr, "ALERT Condition 1 - tp_dp_maxChannelIds < 6 (%d)\n", tp_dp_maxChannelIds);)
      return RULE_MATCH;
   }

   // Using ber_element2 for this one extraction so we can check it and ber_element's sizes later if necessary
   BER_EXTRACT_INT(ber_element2); // Connect-Initial::targetParameters::DomainParameters::maxUserIds
   tp_dp_maxUserIds = ber_element2.data.int_val;
   DEBUG_SO(fprintf(stderr, "target maxUserIds = 0x%04x (%d)\n", tp_dp_maxUserIds, tp_dp_maxUserIds);)

   // Condition 3
   if(tp_dp_maxChannelIds > 6 && tp_dp_maxUserIds > 7) {
      DEBUG_SO(fprintf(stderr, "ALERT Condition 3 - tp_dp_maxChannelIds > 6 (%d) && tp_dp_maxUserIds > 7 (%d)\n", tp_dp_maxChannelIds, tp_dp_maxUserIds);)

      // Special case generated by Wyse OS that has tested to not cause a crash on Windows -- bug 101558
      if(tp_dp_maxChannelIds == 255 && tp_dp_maxUserIds == 255) {
         // The Wyse traffic doesn't cause the issue because it uses a leading null byte when specifying
	 // 0xFF, meaning the MSB isn't sign-extended.  We can tell if this is the case (ie this traffic
	 // is benign) by ensuring that more than one byte is used to represent these values.
	 if(ber_element.data_len > 1 && ber_element2.data_len > 1) {
            DEBUG_SO(fprintf(stderr, "\t** NOALERT ** Special case generated by Wyse OS that has tested to not cause a crash on Windows\n");)
            return RULE_MATCH; //RULE_NOMATCH;
	 } else {
            DEBUG_SO(fprintf(stderr, "\tALERT -- tp_dp_maxChannelIds == 255 && tp_dp_maxUserIds == 255, no null pad\n");)
         }
      }

      return RULE_MATCH;
   }

   // First, pop back to the start of the targetParameters section
   cursor_normal = cursor_targetParameters;
   BER_SKIP(0x30); // Connect-Initial::targetParameters
   BER_SKIP(0x30); // Connect-Initial::minimumParameters
   BER_DATA(0x30); // Connect-Initial::maximumParameters

   BER_EXTRACT_INT(ber_element); // Connect-Initial::maximumParameters::DomainParameters::maxChannelIds
   mp_dp_maxChannelIds = ber_element.data.int_val;
   DEBUG_SO(fprintf(stderr, "maximum maxChannelIds = 0x%04x (%d)\n", mp_dp_maxChannelIds, mp_dp_maxChannelIds);)

   // Condition 2
   if(mp_dp_maxChannelIds < 6) {
      DEBUG_SO(fprintf(stderr, "ALERT Condition 2 - mp_dp_maxChannelIds < 6 (%d)\n", mp_dp_maxChannelIds);)
      return RULE_MATCH;
   }

   return RULE_NOMATCH;
}
/*
Rule *rules[] = {
    &rule21619,
    NULL
};
*/
